// 每一个格子存放的数据
interface Lattice {
  type: ChessTypes | null;
  camp: 0 | 1 | null;
  name: string;
}

interface Point {
  x: number;
  y: number;
}
// 红方将军 黑方将军 游戏结束 游戏进行中
type GameStatus =
  | "RED_CHECK"
  | "BLACK_CHECK"
  | "GAME_OVER"
  | "GAME_RUNNING"
  | "";
enum ChessTypes {
  CAR = "car", // 车 車
  HORSE = "horse", // 马 馬
  CANNON = "cannon", // 炮 砲
  ELEPHANT = "elephant", // 相 象
  SCHOLAR = "scholar", // 仕 士
  KING = "king", // 帥 将
  SOLDIER = "soldier", // 兵 卒
}

abstract class Chess {
  camp: 0 | 1 = 0;
  curX: number;
  curY: number;
  chessType: ChessTypes;
  name: string;
  constructor(x: number, y: number, camp: 0 | 1, chessType: ChessTypes) {
    this.curX = x;
    this.curY = y;
    this.camp = camp;
    this.chessType = chessType;
    this.name = chessMap[chessType][`name_${camp}`];
  }
  move(x: number, y: number) {
    // 是否超出边界
    if (!this.isRange(x, y)) return false;
    // 是否有己方
    if (checkerboard[y][x]?.camp === this.camp) return false;
    // 是否可移动
    const movePoints = this.getShowMovePoint();
    const hasMove = movePoints.some((item) => item.x === x && item.y === y);
    if (!hasMove) return false;
    // 是否有敌方
    const from = checkerboard[this.curY][this.curX];
    checkerboard[this.curY][this.curX] = null;
    checkerboard[y][x] = from;
    this.curX = x;
    this.curY = y;
    return true;
  }
  // 是否在边界
  isRange(x: number, y: number) {
    if (x < 0 || x > RANGE_X - 1) {
      return false;
    }
    if (y < 0 || y > RANGE_Y - 1) {
      return false;
    }
    return true;
  }
  // 获取周围棋子
  getAroundChess(x: number, y: number) {
    if (!this.isRange(x, y)) return {};
    const top = checkerboard[y - 1]?.[x];
    const bottom = checkerboard[y + 1]?.[x];
    const left = checkerboard[y][x - 1];
    const right = checkerboard[y][x + 1];
    const leftTop = checkerboard[y - 1]?.[x - 1];
    const leftBottom = checkerboard[y - 1]?.[x + 1];
    const rightTop = checkerboard[y + 1]?.[x - 1];
    const rightBottom = checkerboard[y + 1]?.[x + 1];
    return {
      top,
      bottom,
      left,
      right,
      leftTop,
      leftBottom,
      rightTop,
      rightBottom,
    };
  }
  // 获取x方向上下一个棋子
  getNextChessX(x: number, y: number) {
    const curRow = checkerboard[y];
    let min = -1;
    let max = -1;
    for (let i = 0; i < curRow.length; i++) {
      if (i > x && curRow[i]?.chessType && max === -1) {
        max = i;
      } else if (i < x && curRow[i]?.chessType) {
        min = i;
      }
    }
    return {
      min: min,
      max: max,
    };
  }
  // 获取y方向上下一个棋子
  getNextChessY(x: number, y: number) {
    let min = -1;
    let max = -1;
    for (let i = 0; i < checkerboard.length; i++) {
      const chess = checkerboard[i][x];
      if (i > y && chess?.chessType && max === -1) {
        max = i;
      } else if (i < y && chess?.chessType) {
        min = i;
      }
    }
    return {
      min: min,
      max: max,
    };
  }
  getShowMovePoint() {
    let res = this.getMovePoint(this.curX, this.curY);
    // 去除己方和当前位置
    res = res
      .filter((v) => this.isRange(v.x, v.y))
      .filter((v) => {
        if (checkerboard[v.y][v.x]?.camp === this.camp) {
          // 己方单位
          return false;
        }
        if (v.x === this.curX && v.y === this.curY) {
          return false;
        }
        return true;
      });
    const newPoint: Point[] = [];
    // 去重
    for (const item of res) {
      if (newPoint.find((v) => item.x === v.x && item.y === v.y)) {
        continue;
      }
      newPoint.push(item);
    }
    return newPoint;
  }
  // 获取可移动的点
  abstract getMovePoint(x: number, y: number): Point[];
}

class CarChess extends Chess {
  constructor(x: number, y: number, camp: 0 | 1) {
    super(x, y, camp, ChessTypes.CAR);
  }
  getMovePoint(x: number, y: number): Point[] {
    const res: Point[] = [];
    const nextChessX = this.getNextChessX(x, y);
    const nextChessY = this.getNextChessY(x, y);
    if (nextChessX.min >= nextChessX.max) {
      nextChessX.max = RANGE_X - 1 - nextChessX.max;
    }
    if (nextChessY.min >= nextChessY.max) {
      nextChessY.max = RANGE_Y - 1 - nextChessY.max;
    }
    for (let x = nextChessX.min; x <= nextChessX.max; x++) {
      res.push({
        x: x,
        y,
      });
    }
    for (let y = nextChessY.min; y <= nextChessY.max; y++) {
      res.push({
        x,
        y: y,
      });
    }
    return res;
  }
}
class HorseChess extends Chess {
  constructor(x: number, y: number, camp: 0 | 1) {
    super(x, y, camp, ChessTypes.HORSE);
  }
  // 显示可移动的点
  getMovePoint(x: number, y: number) {
    const res: Point[] = [];
    const aroundChess = this.getAroundChess(x, y);
    if (!aroundChess.top?.chessType) {
      res.push(
        {
          x: this.curX - 1,
          y: this.curY - 2,
        },
        {
          x: this.curX + 1,
          y: this.curY - 2,
        }
      );
    }
    if (!aroundChess.bottom?.chessType) {
      res.push(
        {
          x: this.curX - 1,
          y: this.curY + 2,
        },
        {
          x: this.curX + 1,
          y: this.curY + 2,
        }
      );
    }
    if (!aroundChess.left?.chessType) {
      res.push(
        {
          x: this.curX - 2,
          y: this.curY - 1,
        },
        {
          x: this.curX - 2,
          y: this.curY + 1,
        }
      );
    }
    if (!aroundChess.right?.chessType) {
      res.push(
        {
          x: this.curX + 2,
          y: this.curY - 1,
        },
        {
          x: this.curX + 2,
          y: this.curY + 1,
        }
      );
    }
    return res;
  }
}
class CannonChess extends Chess {
  constructor(x: number, y: number, camp: 0 | 1) {
    super(x, y, camp, ChessTypes.CANNON);
  }
  getMovePoint(x: number, y: number): Point[] {
    let res: Point[] = [];
    const nextChessX = this.getNextChessX(x, y);
    const nextChessY = this.getNextChessY(x, y);
    if (nextChessX.min >= nextChessX.max) {
      nextChessX.max = RANGE_X - 1 - nextChessX.max;
    }
    if (nextChessY.min >= nextChessY.max) {
      nextChessY.max = RANGE_Y - 1 - nextChessY.max;
    }
    for (let x = nextChessX.min + 1; x < nextChessX.max; x++) {
      res.push({
        x: x,
        y,
      });
    }
    for (let y = nextChessY.min + 1; y < nextChessY.max; y++) {
      res.push({
        x,
        y: y,
      });
    }
    // nextChessX nextChessY 为基准再次寻找下一个是否是敌方
    const { min: xMin } = this.getNextChessX(nextChessX.min, y);
    const { max: xMax } = this.getNextChessX(nextChessX.max, y);
    if (
      checkerboard[y][xMin] !== checkerboard[y][x] &&
      checkerboard[y][xMin]?.camp !== this.camp
    ) {
      res.push({
        x: xMin,
        y,
      });
    }
    if (
      checkerboard[y][xMax] !== checkerboard[y][x] &&
      checkerboard[y][xMax]?.camp !== this.camp
    ) {
      res.push({
        x: xMax,
        y,
      });
    }
    const { min: yMin } = this.getNextChessY(x, nextChessY.min);
    const { max: yMax } = this.getNextChessY(x, nextChessY.max);
    if (
      checkerboard[yMin]?.[x] !== checkerboard[y][x] &&
      checkerboard[yMin]?.[x]?.camp !== this.camp
    ) {
      res.push({
        x,
        y: yMin,
      });
    }
    if (
      checkerboard[yMax]?.[x] !== checkerboard[y][x] &&
      checkerboard[yMax]?.[x]?.camp !== this.camp
    ) {
      res.push({
        x,
        y: yMax,
      });
    }
    // for (const key in aroundChess) {
    //   const item = aroundChess[key as keyof typeof aroundChess];
    //   if (item) {
    //     res = res.filter((v) => v.x !== item.curX && v.y !== item.curY);
    //   }
    // }
    return res;
  }
}
class ElephantChess extends Chess {
  constructor(x: number, y: number, camp: 0 | 1) {
    super(x, y, camp, ChessTypes.ELEPHANT);
  }
  getMovePoint(x: number, y: number): Point[] {
    const res: Point[] = [];
    const aroundChess = this.getAroundChess(x, y);
    if (!aroundChess.leftTop?.chessType) {
      res.push({
        x: this.curX - 2,
        y: this.curY - 2,
      });
    }
    if (!aroundChess.leftBottom?.chessType) {
      res.push({
        x: this.curX - 2,
        y: this.curY + 2,
      });
    }
    if (!aroundChess.rightTop?.chessType) {
      res.push({
        x: this.curX + 2,
        y: this.curY - 2,
      });
    }
    if (!aroundChess.rightBottom?.chessType) {
      res.push({
        x: this.curX + 2,
        y: this.curY + 2,
      });
    }
    return res;
  }
}
class ScholarChess extends Chess {
  constructor(x: number, y: number, camp: 0 | 1) {
    super(x, y, camp, ChessTypes.SCHOLAR);
  }
  getMovePoint(x: number, y: number): Point[] {
    const res: Point[] = [];
    if (this.camp === 0 ? this.curY <= 2 : this.curY >= 7) {
      if (this.curX > 3) {
        res.push(
          {
            x: this.curX - 1,
            y: this.curY === 7 ? -1 : this.curY - 1,
          },
          {
            x: this.curX - 1,
            y: this.curY === 2 ? -1 : this.curY + 1,
          }
        );
      }
      if (this.curX < 5) {
        res.push(
          {
            x: this.curX + 1,
            y: this.curY === 7 ? -1 : this.curY - 1,
          },
          {
            x: this.curX + 1,
            y: this.curY === 2 ? -1 : this.curY + 1,
          }
        );
      }
    }

    return res;
  }
}
class KingChess extends Chess {
  constructor(x: number, y: number, camp: 0 | 1) {
    super(x, y, camp, ChessTypes.KING);
  }
  getMovePoint(x: number, y: number) {
    const res: Point[] = [];
    if (this.camp === 0 ? this.curY > 0 : this.curY > 7) {
      res.push({
        x: this.curX,
        y: this.curY - 1,
      });
    }
    if (this.camp === 0 ? this.curY < 2 : this.curY > 6) {
      res.push({
        x: this.curX,
        y: this.curY + 1,
      });
    }
    if (this.curX > 3) {
      res.push({
        x: this.curX - 1,
        y: this.curY,
      });
    }
    if (this.curX < 5) {
      res.push({
        x: this.curX + 1,
        y: this.curY,
      });
    }
    return res;
  }
}
class SoldierChess extends Chess {
  constructor(x: number, y: number, camp: 0 | 1) {
    super(x, y, camp, ChessTypes.SOLDIER);
  }
  getMovePoint(x: number, y: number) {
    const res: Point[] = [];
    if (this.camp === 1) {
      res.push({
        x: this.curX,
        y: this.curY - 1,
      });
    }
    if (this.camp === 0) {
      res.push({
        x: this.curX,
        y: this.curY + 1,
      });
    }
    if (this.camp === 0 ? this.curY > 4 : this.curY < 5) {
      res.push({
        x: this.curX - 1,
        y: this.curY,
      });
    }
    if (this.camp === 0 ? this.curY > 4 : this.curY < 5) {
      res.push({
        x: this.curX + 1,
        y: this.curY,
      });
    }
    return res;
  }
}

// 范围常量
const RANGE_X = 9;
const RANGE_Y = 10;
let checkerboard: (Chess | null)[][] = [];

// 0 黑 1 红
const chessMap = {
  [ChessTypes.CAR]: {
    name_0: "車",
    name_1: "车",
    Constructor: CarChess,
  },
  [ChessTypes.HORSE]: {
    name_0: "馬",
    name_1: "马",
    Constructor: HorseChess,
  },
  [ChessTypes.CANNON]: {
    name_0: "砲",
    name_1: "炮",
    Constructor: CannonChess,
  },
  [ChessTypes.ELEPHANT]: {
    name_0: "象",
    name_1: "相",
    Constructor: ElephantChess,
  },
  [ChessTypes.SCHOLAR]: {
    name_0: "士",
    name_1: "仕",
    Constructor: ScholarChess,
  },
  [ChessTypes.KING]: {
    name_0: "将",
    name_1: "帥",
    Constructor: KingChess,
  },
  [ChessTypes.SOLDIER]: {
    name_0: "卒",
    name_1: "兵",
    Constructor: SoldierChess,
  },
};

class Game {
  RANGE_X = 9; // 棋盘 x
  RANGE_Y = 10; // 棋盘 y
  chessSize = 30; // 棋子大小
  cellSize = 50; // 棋盘格子大小
  isGameOver = false; // 是否游戏结束
  currentOperator = 1; // 当前操作方
  aiCamp = 0; // 人机阵营
  selectedChess: HTMLElement | null = null;
  container: HTMLElement | null = null;
  tips: HTMLElement | null = null;
  status: GameStatus = "";
  baseChess = [
    { x: 0, y: 0, camp: 0, type: ChessTypes.CAR },
    { x: 1, y: 0, camp: 0, type: ChessTypes.HORSE },
    { x: 2, y: 0, camp: 0, type: ChessTypes.ELEPHANT },
    { x: 3, y: 0, camp: 0, type: ChessTypes.SCHOLAR },
    { x: 4, y: 0, camp: 0, type: ChessTypes.KING },
    { x: 5, y: 0, camp: 0, type: ChessTypes.SCHOLAR },
    { x: 6, y: 0, camp: 0, type: ChessTypes.ELEPHANT },
    { x: 7, y: 0, camp: 0, type: ChessTypes.HORSE },
    { x: 8, y: 0, camp: 0, type: ChessTypes.CAR },
    { x: 1, y: 2, camp: 0, type: ChessTypes.CANNON },
    { x: 7, y: 2, camp: 0, type: ChessTypes.CANNON },
    { x: 0, y: 3, camp: 0, type: ChessTypes.SOLDIER },
    { x: 2, y: 3, camp: 0, type: ChessTypes.SOLDIER },
    { x: 4, y: 3, camp: 0, type: ChessTypes.SOLDIER },
    { x: 6, y: 3, camp: 0, type: ChessTypes.SOLDIER },
    { x: 8, y: 3, camp: 0, type: ChessTypes.SOLDIER },
    { x: 0, y: 6, camp: 1, type: ChessTypes.SOLDIER },
    { x: 2, y: 6, camp: 1, type: ChessTypes.SOLDIER },
    { x: 4, y: 6, camp: 1, type: ChessTypes.SOLDIER },
    { x: 6, y: 6, camp: 1, type: ChessTypes.SOLDIER },
    { x: 8, y: 6, camp: 1, type: ChessTypes.SOLDIER },
    { x: 1, y: 7, camp: 1, type: ChessTypes.CANNON },
    { x: 7, y: 7, camp: 1, type: ChessTypes.CANNON },
    { x: 0, y: 9, camp: 1, type: ChessTypes.CAR },
    { x: 1, y: 9, camp: 1, type: ChessTypes.HORSE },
    { x: 2, y: 9, camp: 1, type: ChessTypes.ELEPHANT },
    { x: 3, y: 9, camp: 1, type: ChessTypes.SCHOLAR },
    { x: 4, y: 9, camp: 1, type: ChessTypes.KING },
    { x: 5, y: 9, camp: 1, type: ChessTypes.SCHOLAR },
    { x: 6, y: 9, camp: 1, type: ChessTypes.ELEPHANT },
    { x: 7, y: 9, camp: 1, type: ChessTypes.HORSE },
    { x: 8, y: 9, camp: 1, type: ChessTypes.CAR },
  ];
  redUnlimited: boolean = false;
  isAddAi: boolean = true;
  currentChessCount = 0;

  constructor(redUnlimited: boolean = false, isAddAi: boolean = true) {
    this.redUnlimited = redUnlimited;
    this.isAddAi = isAddAi;
    checkerboard = new Array(this.RANGE_Y)
      .fill(0)
      .map((_) => new Array(this.RANGE_X).fill(null));
    if (globalThis.document) {
      this.initDom();
    }
    this.resetCheckerboard();
    this.currentChessCount = this.baseChess.length;
  }
  initDom() {
    this.container = document.querySelector(".container")!;
    this.tips = document.querySelector(".tips span") as HTMLSpanElement;
    document.documentElement.style.setProperty(
      "--chessSize",
      `${this.chessSize}px`
    );
    document.documentElement.style.setProperty(
      "--cellSize",
      `${this.cellSize}px`
    );
    document.documentElement.style.setProperty("--x", this.RANGE_X.toString());
    document.documentElement.style.setProperty("--y", this.RANGE_Y.toString());
    this.container?.addEventListener("click", (e) => {
      if (this.isGameOver) {
        if (confirm("游戏结束, 是否重新开始?")) {
          this.resetCheckerboard();
        } else {
          this.resetCheckerboard();
        }
        return;
      }
      const el = e.target as HTMLElement;
      this.handleMove(el);
    });
  }
  handleMove(el: HTMLElement) {
    const toX = parseInt(el.dataset.x!);
    const toY = parseInt(el.dataset.y!);
    // 点击不是棋子 或 点自己
    if (!el.classList.contains("inner") || el.classList.contains("selected")) {
      return;
    }
    // 没有选中棋子
    if (el.classList.contains("empty") && !this.selectedChess) {
      return;
    }
    // 选中 点击空白 移动
    if (el.classList.contains("empty") && this.selectedChess) {
      const fromX = parseInt(this.selectedChess.dataset.x!);
      const fromY = parseInt(this.selectedChess.dataset.y!);
      const chess = checkerboard[fromY][fromX];
      if (chess) {
        const isMove = chess.move(toX, toY);
        if (isMove) {
          this.onMove(chess);
        } else {
          this.onNotMove();
        }
      }
      return;
    }
    // 点击的不是空白 选中棋子
    if (!el.classList.contains("empty")) {
      if (this.selectedChess) {
        const fromX = parseInt(this.selectedChess.dataset.x!);
        const fromY = parseInt(this.selectedChess.dataset.y!);
        const toChess = checkerboard[toY][toX];
        const fromChess = checkerboard[fromY][fromX];
        if (toChess?.camp !== fromChess?.camp) {
          const isMove = fromChess?.move(toX, toY);
          if (isMove) {
            this.onMove(fromChess as Chess);
            return;
          } else {
            this.onNotMove();
          }
        }
      }
      this.selectedChess?.classList.remove("selected");
      if (this.currentOperator === checkerboard[toY]?.[toX]?.camp) {
        el.classList.add("selected");
        this.onSelect(checkerboard[toY]?.[toX]);
        this.selectedChess = el;
      }
    }
  }
  resetCheckerboard() {
    checkerboard = checkerboard.map((v) => v.map((v) => null));
    this.isGameOver = false;
    this.currentOperator = 1;
    this.status = "GAME_RUNNING";
    for (const bc of this.baseChess) {
      const { x, y, camp, type } = bc;
      const { Constructor } = chessMap[type];
      const chess = new Constructor(x, y, camp as 0 | 1);
      checkerboard[y][x] = chess;
    }
    this.draw();
  }
  draw() {
    if (!this.container) return;
    this.container.innerHTML = "";
    for (let i = 0; i < checkerboard.length; i++) {
      const row = checkerboard[i];
      for (let j = 0; j < row.length; j++) {
        const chess = row[j];
        const item = document.createElement("div");
        const inner = document.createElement("div");
        item.appendChild(inner);
        item.classList.add("item");
        inner.classList.add("inner");
        if (i === RANGE_Y - 1 || j === RANGE_X - 1) {
          item.classList.add("not_border");
        }
        if (i === 4) {
          if (j === 0 || j === RANGE_X - 1) {
            item.classList.add("range");
          } else {
            item.classList.add("split");
          }
        }
        this.container.appendChild(item);
        inner.dataset.x = j.toString();
        inner.dataset.y = i.toString();
        if (chess) {
          inner.classList.add(chess.camp === 0 ? "black" : "red");
          inner.textContent = chess.name;
        } else {
          inner.classList.add("empty");
        }
      }
    }
  }
  onMove(chess: Chess) {
    if (this.selectedChess) {
      this.selectedChess.classList.remove("selected");
    }
    const chesses = checkerboard.flat(2).filter(Boolean).length;
    if (chesses !== this.currentChessCount) {
      // 棋子被吃掉了
      this.currentChessCount = chesses;
      this.onEatChess();
    }
    this.draw();
    this.selectedChess = null;
    if (this.redUnlimited) {
      this.currentOperator = 1;
    } else {
      this.currentOperator = +!this.currentOperator;
    }
    this.tips!.textContent = this.currentOperator ? "红方" : "黑方";
    this.tips!.style.color = this.currentOperator ? "#f00" : "#000";

    document.querySelectorAll(".can_move").forEach((el) => {
      el.classList.remove(".can_move");
    });
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        if (this.isWin()) {
        } else {
          this.isGeneral(chess);
          this.isAddAi &&
            this.aiCamp === this.currentOperator &&
            this.startAi();
        }
      });
    });
  }
  onEatChess() {
    this.showTips("吃", "warning");
  }
  // 是否将军 chess 刚刚移动的棋子
  isGeneral(chess: Chess) {
    if (!chess) return;
    const nextMovePoint = chess.getShowMovePoint(); // 获取下一次可移动的范围
    for (const point of nextMovePoint) {
      const { x, y } = point;
      if (checkerboard[y]?.[x]?.chessType === ChessTypes.KING) {
        // 存在将军
        this.status = chess.camp === 0 ? "BLACK_CHECK" : "RED_CHECK";
        this.showTips("将军", "warning");
        return;
      }
    }
  }
  isWin() {
    const kings = checkerboard
      .flat(2)
      .filter((v) => v?.chessType === ChessTypes.KING);
    if (kings.length !== 2) {
      const black = kings.find((v) => v?.camp === 0);
      this.isGameOver = true;
      this.status = "GAME_OVER";
      if (!black) {
        this.showTips("红方胜利", "success");
      } else {
        this.showTips("黑方胜利", "success");
      }
      return true;
    }
    return false;
  }
  startAi(camp: 0 | 1 = 0) {
    const chesses = checkerboard.flat(2).filter((v) => v?.camp === camp);
    const move = () => {
      const index = Math.floor(Math.random() * chesses.length);
      const moveRange = chesses[index]!.getShowMovePoint();
      const moveIndex = Math.floor(Math.random() * moveRange.length);
      const point = moveRange[moveIndex];
      if (!point) {
        move();
        return;
      }
      const isMove = chesses[index]?.move(point.x, point.y);
      if (isMove) {
        this.draw();
        this.currentOperator = +!this.currentOperator;
        this.tips!.textContent = this.currentOperator ? "红方" : "黑方";
        this.tips!.style.color = this.currentOperator ? "#f00" : "#000";
        return;
      } else {
        move();
      }
    };
    move();
  }
  onNotMove() {
    this.showTips("不能执行此操作", "error");
  }
  onSelect(chess: Chess) {
    if (chess) {
      document.querySelectorAll(".can_move").forEach((el) => {
        el.classList.remove("can_move");
      });
      const moveRange = chess.getShowMovePoint();
      for (const item of moveRange) {
        const { x, y } = item;
        const inner = document.querySelector(
          `.inner[data-x="${x}"][data-y="${y}"]`
        ) as HTMLElement;
        inner.classList.add("can_move");
      }
    }
  }
  showTips(content: string, type: "success" | "warning" | "error" = "success") {
    const tips = document.createElement("div");
    tips.classList.add("game_tips", type);
    const text = document.createElement("div");
    text.textContent = content;
    tips.appendChild(text);
    document.body.appendChild(tips);
    tips.addEventListener("animationend", () => {
      tips.remove();
    });
  }
}
new Game(false, false);
